О специальных макро в ассемблере

Введение

Много лет назад американским специалистом Гарри Килдэллом (Gary Kildall) в рамках создания системы программирования для персональных компьютеров был разработан транслятор с языка ассемблера для процессора Intel 8086, который он назвал RASM-86 (Relocating ASseMbler). Этот во многом типичный для своего времени продукт имел особенность: он позволял, не меняя транслятора, добавлять описания новых команд процессора с помощью специальных макросредств.

Автор статьи, используя и развивая этот транслятор, успешно применял данные средства по мере появления новых поколений процессоров. Конечно, иногда и сам транслятор требовал ряда доработок, например, при переходе на архитектуру IA-32, а затем и на x86-64 (IA-32e). Тем не менее, изначально заложенная идея позволила легко продолжать эволюцию транслятора до настоящего времени. Некоторые итоги этой работы рассматриваются далее.

Организация генерации команд в трансляторе с ассемблера

Обычно программистам безразлично, как именно организована генерация кодов команд внутри используемого ими транслятора. Однако, поскольку в данном случае можно влиять на этот процесс, рассмотрим на примере конкретного транслятора ход генерации.

Транслятор RASM имеет общую внутреннюю таблицу всех возможных команд процессора. Элементы таблицы начинаются с текста мнемоники команды, за которым следует связанный список всех возможных форм данной команды в зависимости от числа, типа и размера ее операндов. Содержимое очередной формы команды состоит из последовательности «микрокодов», каждый из которых представляет отдельно обрабатываемую и уже неделимую часть команды.

Сначала транслятор ищет заданную команду в таблице по ее мнемонике, а затем, идя по связанному списку, ищет подходящую комбинацию операндов. Если заданной комбинации операндов (включая и случай отсутствия операндов в команде) не найдено – выдается сообщение об ошибке. Иначе транслятор читает последовательность «микрокодов» и выполняет каждый из них, составляя, таким образом, код заданной команды. При этом частные случаи комбинации операндов должны располагаться ранее общих случаев. Например, если команда имеет отдельную форму для операнда-байта, расширяемого со знаком, такой операнд должен встретиться в связанном списке раньше общего случая операнда-константы иначе, естественно, будет находиться только общий случай.

Идея Килдэлла состояла в том, что программист с помощью специальных макросредств может описать новую команду или новую форму с новой комбинацией операндов для уже существующей команды или даже повторить уже существующую комбинацию операндов, но указать новое содержимое. Команда описывается по тем же правилам, по которым были первоначально описаны все команды при создании самого транслятора.

Трехпроходный транслятор RASM на первом проходе переведет это описание в последовательность «микрокодов» и вставит новое описание в общую таблицу команд. В этом принципиальное отличие данных макросредств от «обычных», которыми можно изменить исходный текст программы, но нельзя изменить содержимое самого транслятора. Если такая команда уже существовала, новое описание добавляется в начало имеющегося связанного списка. Если уже существовала и такая команда и такая комбинация операндов для нее – новое описание отменит предыдущее, так как будет вставлена «выше». После этого в тексте программы можно свободно использовать новую команду так же, как если бы она изначально была представлена в таблице транслятора.

Макросредства описания команд

По виду специальные макросредства RASM похожи на обычные средства макроподстановки: имеются ключевые слова CodeMacro и EndM, между которыми пишется «тело» макроопределения. В первой строке пишется имя макро и, возможно, список его параметров. Например:

CodeMacro AAA   DB 37H EndM  CodeMacro DIV divisor:Eb SEGFIX divisor   DB 6FH EndM  CodeMacro OR dst:Re, src:Ee   SEGFIX src   DB 0BH   MODRM dst,src EndM

Описание формальных параметров

Каждый формальный параметр в макро имеет имя, задаваемое программистом, и после двоеточия спецификацию, состоящую из типа, размера и при необходимости диапазона допустимых значений в круглых скобках.

Типы формальных параметров:

A – сумматор EAX/AX/AL C – выражение типа метка  D – непосредственный операнд E – адресное выражение, записанное в регистре или памяти M – адресное выражение, может иметь базовые и индексные регистры R – один из общих регистров S – сегментный регистр X – прямое обращение к памяти при обмене с сумматором

Размеры формальных параметров

n - длина неопределенна b – байт w – слово e – двойное слово d – длина при использовании адреса смещение+сегмент sb – знаковый байт, расширяемый до слова se – знаковый байт, расширяемый до двойного слова

Примеры описания формальных параметров:

CodeMacro IN dst:Aw, port:Rw (DX) CodeMacro ROR dst:Ee, count:Rb (CL)

Директивы макроопределений

Первоначально все описания команд х86 свелись к нескольким директивам, часть из которых используются редко. Перевод транслятора на архитектуру IA-32 потребовал добавления лишь одной новой директивы управления префиксами размера/адреса 66H/67H, причем, чтобы не вводить новых ключевых слов используется уже имевшаяся директива, но с другой формой параметра.

Директивы DB, DW и DD

Данные директивы в макро почти эквиваленты обычным операторам ассемблера и используются для задания констант и адресов. Эти директивы характерны и для любых других (не специальных) макросредств.

Директива DW используется для задания адреса (4 байта в 32-х разрядном режиме), а директива DD – для задания адреса в виде смещение+сегмент. Примеры использования DB, DW и DD:

CodeMacro CLC   DB 0F8H EndM  CodeMacro XOR dst:Ee,src:De   SEGFIX dst   DB 81H   MODRM 6,dst   DW src EndM  CodeMacro CALLF label:Cd   DB 9AH   DD label EndM

Директива адресации MODRM

Это главная из «специальных» директив, определяющая адресацию архитектуры IA-32. Она определяет и основное отличие данных макросредств от обычных. Именно «микрокод», порождаемый этой директивой, указывает транслятору генерировать адресную часть команды, включая и байт режимов адресации и смещение и SIB-байт, если операнды подразумевают это. Директива имеет два параметра. Это или два имени формальных параметров макро или константа-число и имя. Например:

CodeMacro RCR dst:Ee, count:Rb(CL)   SEGFIX dst   DB 0D3H   MODRM 3,dst EndM  CodeMacro XOR dst:Re,src:Ee   SEGFIX src   DB 33H   MODRM dst,src EndM

Директивы определения относительного адреса RELB, RELW

Эти директивы используются для описания команд передачи управления по относительному адресу, занимающему или байт или 4 байта для IA-32. Пример:

CodeMacro LOOP place:Cb   DB 0E2H   RELB place EndM

Директива задания кодов DBIT

Директива позволяет прямо сформировать цепочку бит, подставив в нее параметры. В ней указывается список полей через запятую. Для каждого поля задается его размер в битах, значение как константа или как имя формального параметра вместе с указанием в круглых скобках сдвига этого параметра вправо, например:

CodeMacro DEC dst:Re   DBIT 5(9), 3(dst(0)) EndM

Директива формирования префикса сегмента SEGFIX

Параметром данной директивы является имя формального параметра. Директива указывает транслятору, что если заданный параметр находится не в сегменте, который предполагается по умолчанию, необходимо генерировать соответствующий префикс сегментного регистра.

Директива контроля сегментов NOSEGFIX

Директива имеет параметры в виде имени сегментного регистра и имени формального параметра. Она не генерирует кода, а проверяет, что обращение к данному параметру идет с использованием указанного сегментного регистра, иначе сообщает об ошибке. Эта директива требуется лишь в общих формах команд CMPS и MOVS, где один из операндов может адресоваться только через ES.

Данная директива была расширена для управления префиксами размера и адреса 66H/67H. В этом случае в директиве указывается параметр-число: 0 – нет префиксов, 1- может быть префикс 66H, 2 – может быть префикс 67H, 3 – могут быть оба префикса, 4 – всегда есть оба, 5 – никогда нет 66H, 6 – никогда нет 67H и т.п.

Такими простыми средствами удается описать все множество команд IA-32, например:

CodeMacro FLDCW src:Mw   SEGFIX src   DB 0D9H   MODRM 5, src EndM  CodeMacro CMOVAE dst:Re, src:Ee   SEGFIX src   DB 0FH   DB 43H   MODRM dst,src EndM

Некоторое исключение из стройной системы описаний составляют команды FPU, имеющие операнд в памяти. Для простоты в RASM разрядность таких команд указывается прямо в мнемонике, а не определяется по размеру операнда в памяти. Поэтому в RASM есть, например, команды FIST16, FIST32 и FIST64. Однако на практике, с точки зрения ясности текста, указание разрядности операнда прямо в имени команды FPU оказалось вполне приемлемым.

Создание псевдокоманд с помощью макросредств

Используя возможность добавления новых комбинаций операндов можно конструировать новые «команды» процессора. Например, команду MOV ECX,10 часто целесообразно заменять двумя командами с более коротким кодом PUSH 10 и POP ECX. А эти две команды можно описать в виде одного макроопределения:

CodeMacro MOVSX dst:Re, src:Dse   NOSEGFIX 6   DB 6AH   DB src   DBIT 5(0BH),3(dst(0)) EndM

Такую псевдокоманду можно создать и «обычными» макросредствами, однако назвать ее именем уже существующей команды MOVSX другие трансляторы (кроме RASM), скорее всего, не позволят. С точки зрения результата, это именно выполнение MOVSX с параметром-константой. Но такой команды в процессоре нет. А с макросредствами RASM можно считать, что на самом деле есть такая формы команды. Тот факт, что реально выполнение идет за два приема и стек меняется, а затем восстанавливается, в большинстве случаев можно не учитывать.

За время использования транслятора RASM накопился ряд таких полезных псевдокоманд, например:

MOV X,Y, где X,Y переменные в памяти;

MOV DS,0 или MOV DS,ES;

Команды PUSH и POP для нескольких регистров сразу, т.е. PUSH EAX,EBX,ECX;

Обращение к портам без указания регистра DX и т.п.

Добавление новых типов команд

Но, конечно, главное назначение описываемых средств – это